/**
/**
 * JAVACC DEMO 1.0
 */

package com.apache.excp.core.impl.helper;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.apache.excp.Utils;

import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.MultiTemplateLoader;
import freemarker.cache.StringTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.core.Environment;
import freemarker.core.ExistMethodModel;
import freemarker.core.ExtendsDirective;
import freemarker.core.FormatDirective;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * description:  使用freemarker生成文件
 * @author Hou Dayu 创建时间：2016-7-28
 */
public class FreemarkerHelper {

	private static Logger LOG = LoggerFactory.getLogger(FreemarkerHelper.class);
	private static final String DEFAULT_ENCODING = "UTF-8";

	public static Configuration createConfiguration() {
		Configuration configuration = new Configuration();
		configuration.setSharedVariable("extends", new ExtendsDirective());
		configuration.setSharedVariable("format", new FormatDirective());
		configuration.setSharedVariable("exist", new ExistMethodModel());
		return configuration;
	}

	/**
	 * description:  加载模板
	 * @param templatePath 模板文件
	 * @return
	 * @throws IOException   
	 * @author Hou Dayu  
	 * @update 2014-7-29
	 */
	public static TemplateLoader createTemplateLoader(String templatePath) throws IOException {
		if (templatePath == null) {
			return new ClassTemplateLoader(FreemarkerHelper.class, "/");
		}
		List<TemplateLoader> loaders = new ArrayList<TemplateLoader>();

		File curf = new File(templatePath);
		if (curf.exists()) {
			loaders.add(new FileTemplateLoader(curf));
			LOG.debug("load template from " + curf);
		}

		URL classUrl = FreemarkerHelper.class.getProtectionDomain().getCodeSource().getLocation();
		if ("file".equals(classUrl.getProtocol())) {
			String parent = new File(classUrl.getPath()).getParent();
			File clsf = new File(parent, templatePath);
			if (clsf.exists()) {
				loaders.add(new FileTemplateLoader(clsf));
				LOG.debug("load template from " + clsf);
			}
		}

		loaders.add(new ClassTemplateLoader(FreemarkerHelper.class, templatePath));

		TemplateLoader[] lds = new TemplateLoader[loaders.size()];
		for (int i = 0; i < loaders.size(); i++) {
			lds[i] = ((TemplateLoader) loaders.get(i));
		}
		return new MultiTemplateLoader(lds);
	}

	public static StringWriter produceAsStringWriter(Map<String, Object> context, String templatePath,
			String templateName) throws TemplateException, IOException {
		return produceAsStringWriter(null, context, DEFAULT_ENCODING, templatePath, templateName, DEFAULT_ENCODING);
	}

	public static StringWriter produceAsStringWriter(Map<String, Object> context, String outEncoding,
			String templatePath, String templateName, String templateEncoding) throws TemplateException, IOException {
		return produceAsStringWriter(null, context, outEncoding, templatePath, templateName, templateEncoding);
	}

	public static StringWriter produceAsStringWriter(TemplateLoader loader, Map<String, Object> context,
			String outEncoding, String templatePath, String templateName, String templateEncoding)
			throws TemplateException, IOException {
		Configuration cfg = createConfiguration();
		if (loader == null)
			cfg.setTemplateLoader(createTemplateLoader(templatePath));
		else {
			cfg.setTemplateLoader(loader);
		}

		StringWriter writer = new StringWriter(1024);
		BufferedWriter bw = new BufferedWriter(writer);
		Template template = getTemplate(cfg, templateEncoding, templateName);

		Environment env = template.createProcessingEnvironment(createContext(context), bw);
		if (outEncoding != null) {
			env.setOutputEncoding(outEncoding);
		}
		env.process();

		bw.flush();

		String tempResult = writer.toString();
		if ((tempResult != null) && (tempResult.trim().length() == 0)) {
			LOG.debug("Generated output is empty.");
			return null;
		}

		return writer;
	}

	private static SimpleHash createContext(Map<String, Object> context) {
		SimpleHash ctx = new SimpleHash(ObjectWrapper.BEANS_WRAPPER);
		ctx.put("utils", new Utils());
		if (context != null) {
			Iterator<Entry<String, Object>> iterator = context.entrySet().iterator();
			while (iterator.hasNext()) {
				Map.Entry<String, Object> element = (Map.Entry<String, Object>) iterator.next();
				String key = (String) element.getKey();
				Object value = element.getValue();

				ctx.put(key, value);
			}
		}
		return ctx;
	}

	/**
	 * description:  获取模板模型
	 */
	public static Template getTemplate(Configuration cfg, String encoding, String template) throws IOException {
		if (encoding == null) {
			return cfg.getTemplate(template);
		}
		return cfg.getTemplate(template, encoding);
	}

	public static void produce(Map<String, Object> context, File outFile, String templatePath, String templateName)
			throws IOException, TemplateException {
		produce(null, context, DEFAULT_ENCODING, outFile, templatePath, templateName, DEFAULT_ENCODING);
	}

	public static void produce(Map<String, Object> context, String outEncoding, File outFile, String templatePath,
			String templateName, String templateEncoding) throws IOException, TemplateException {
		produce(null, context, outEncoding, outFile, templatePath, templateName, templateEncoding);
	}

	/**
	 * description:  利用freemarker生成文件
	 */
	public static void produce(TemplateLoader loader, Map<String, Object> context, String outEncoding, File outFile,
			String templatePath, String templateName, String templateEncoding) throws IOException, TemplateException {
		Configuration cfg = createConfiguration();
		if (loader == null)
			cfg.setTemplateLoader(createTemplateLoader(templatePath));
		else {
			cfg.setTemplateLoader(loader);
		}

		boolean ex = outFile.exists();
		ensureExistence(outFile);

		Template template = getTemplate(cfg, templateEncoding, templateName);

		Writer writer = null;
		try {
			if (outEncoding == null)
				writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile)));
			else {
				writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), outEncoding));
			}
			Environment env = template.createProcessingEnvironment(createContext(context), writer);
			if (outEncoding != null) {
				env.setOutputEncoding(outEncoding);
			}
			env.process();
		} finally {
			if (writer != null) {
				try {
					writer.flush();
				} catch (IOException localIOException) {
				}
				writer.close();
			}
		}

		if (ex)
			LOG.warn("Replace " + outFile.getAbsolutePath());
		else
			LOG.info(outFile.getAbsolutePath());
	}

	private static void ensureExistence(File destination) {
		File dir = destination.getAbsoluteFile().getParentFile();
		if ((dir.exists()) && (!dir.isDirectory())) {
			throw new RuntimeException("The path: " + dir.getAbsolutePath() + " exists, but is not a directory");
		}
		if ((!dir.exists()) && (!dir.mkdirs())) {
			if ((dir.getName().equals(".")) && (dir.getParentFile().mkdirs())) {
				return;
			}

			throw new RuntimeException("unable to create directory: " + dir.getAbsolutePath());
		}
	}

	public static String produceAsString(Map<String, Object> context, String ftl) {
		return produceAsString(context, DEFAULT_ENCODING, ftl, DEFAULT_ENCODING);
	}

	public static String produceAsString(Map<String, Object> context, String outEncoding, String ftl,
			String templateEncoding) {
		try {
			String templateName = "stringTemplate";
			Configuration cfg = createConfiguration();
			StringTemplateLoader templateLoader = new StringTemplateLoader();
			templateLoader.putTemplate(templateName, ftl);
			cfg.setTemplateLoader(templateLoader);
			if (templateEncoding != null) {
				cfg.setDefaultEncoding(templateEncoding);
			}

			Template template = getTemplate(cfg, templateEncoding, templateName);

			StringWriter writer = new StringWriter(1024);

			Environment env = template.createProcessingEnvironment(createContext(context), writer);
			if (outEncoding != null) {
				env.setOutputEncoding(outEncoding);
			}
			env.process();

			return writer.toString();
		} catch (Exception e) {
			LOG.error(ftl);
			LOG.error(e.getMessage());
			throw new RuntimeException(e);
		}
	}
}
